﻿#include "ldb.h"
#include "ldb_file.h"
#include "ldb_util.h"
#include "../nedmalloc/NedAllocatorImpl.h"
//#include "../../include/lua_extend.h"

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

#define LDB_MAX_INPUT 200
#define LDB_MAX_PARAM 5
#define BUFFER_SIZE 1024

static const char* lua_tag = "__ldb_tag";
lua_State* g_lua_state = NULL;

typedef struct input_t {
	char    buffer[LDB_MAX_PARAM][LDB_MAX_INPUT];
	int     num;
} input_t;

struct socket {
	int listen_fd;
	int fd;
	int closed;
};

static const char *prompt = "(ldb)";
struct socket *debugsocket = NULL;

static void print_table_var(lua_State *state, int si, int depth);
static int help_handler(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input);
static int exit_debugger(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input);
static int backtrace_handler(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input);
static int print_handler(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input);
static int break_handler(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input);
static void all_hook(lua_State *state, lua_Debug *ar);
static void enable_line_hook(lua_State *state, int enable);
static void step_in(lua_State *state, ldb_t *ldb, int depth, lua_Debug *ar);
static int next_handler(lua_State *state, ldb_t *ldb, lua_Debug *ar, input_t *input);
static void single_step(ldb_t *ldb, int step);
static int list_handler(lua_State *state, ldb_t *ldb, lua_Debug *ar, input_t *input);

typedef int(*handler_pt)(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input);

typedef struct ldb_command_t {
	const char* name;
	const char* short_name;
	const char* help;
	handler_pt  handler;
} ldb_command_t;

ldb_command_t commands[] = {
  {
	"help",
	"h",
	": print help info",
	&help_handler
  },

  {
	"backtrace",
	"bt",
	": print backtrace info",
	&backtrace_handler
  },

  {
	"print",
	"p",
	"<varname> : print var value",
	&print_handler
  },

  {
	"break",
	"b",
	"[function|filename:line] : break at function or line in a file",
	&break_handler
  },

  {
	"next",
	"n",
	":next line",
	&next_handler
  },

  {
	"exit",
	"e",
	":exit debugger",
	&exit_debugger
  },

  {
	"list",
	"l",
	": list file source",
	&list_handler
  },

  {NULL,        NULL, NULL,                                 NULL},
};

// 得到luastate
static lua_State* get_luastate(void)
{
    return g_lua_state;
}

static int
add_line_break_point(ldb_t *ldb, const char *file, int line) {
	int i;
	ldb_breakpoint_t *bkpoint;

	for (i = 0; i < MAX_BREAKPOINT; ++i) {
		if (ldb->bkpoints[i].available) {
			break;
		}
	}

	if (i == MAX_BREAKPOINT) {
		return -1;
	}
	bkpoint = &(ldb->bkpoints[i]);
	bkpoint->available = 0;
	bkpoint->file = strdup(file);
	bkpoint->line = line;
	bkpoint->active = 1;
	bkpoint->index = i;
	bkpoint->hit = 0;
	bkpoint->func = NULL;
	ldb->bknum += 1;

	return i;
}

static void
handle_line_break(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input,
	char *dv) {
	char name[200];
	int line, index;
	ldb_file_t *file;

	line = atoi(dv + 1);
	*dv = '\0';
	strcpy(name, input->buffer[1]);

	file = ldb_file_load(ldb, name);
	if (file == NULL) {
		return;
	}
	if (line > file->line) {
		ldb_write(get_luastate(), "line too long\n");
		return;
	}
	index = add_line_break_point(ldb, name, line - 1);
	if (index < 0) {
		return;
	}

	char tempStr[1024];
	sprintf(tempStr,"breakpoint %d is set at %s:%d\n",
		index, name, line);
	ldb_write(get_luastate(), tempStr);
	enable_line_hook(state, 1);
}

static int
add_func_break_point(ldb_t *ldb, const char *func, const char *type) {
	int i;
	ldb_breakpoint_t *bkpoint;

	for (i = 0; i < MAX_BREAKPOINT; ++i) {
		if (ldb->bkpoints[i].available) {
			break;
		}
	}

	if (i == MAX_BREAKPOINT) {
		return -1;
	}
	bkpoint = &(ldb->bkpoints[i]);
	bkpoint->available = 0;
	bkpoint->file = NULL;
	bkpoint->func = strdup(func);
	bkpoint->line = -1;
	bkpoint->active = 1;
	bkpoint->index = i;
	bkpoint->hit = 0;
	bkpoint->type = type;
	ldb->bknum += 1;

	return i;
}

static void
enable_func_hook(lua_State *state, ldb_t *ldb, int enable) {
	int mask;

	mask = lua_gethookmask(state);
	if (enable) {
		lua_sethook(state, all_hook, mask | LUA_MASKCALL, 0);
		ldb->step = 0;
	}
	else {
		lua_sethook(state, all_hook, mask & ~LUA_MASKCALL, 0);
	}
}

static void
handle_func_break(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	int top, index, len, pos;
	char *p, *buffer;

	top = lua_gettop(state);
	index = -2;
	buffer = &(input->buffer[1][0]);
	len = strlen(buffer);
	p = strchr(buffer, '.');
	while (p) {
		lua_pushlstring(state, buffer, p - buffer);
		lua_gettable(state, index);
		if (lua_isnil(state, -1)) {
			lua_settop(state, top);

			char tempStr[1024];
			sprintf(tempStr,"Table '%.*s' not found!\n", p - buffer, buffer);
			ldb_write(get_luastate(), tempStr);

			return;
		}
		index = lua_gettop(state);
		buffer = p + 1;
		p = strchr(buffer, '.');
	}

	pos = buffer - &(input->buffer[1][0]);
	lua_pushstring(state, buffer);
	lua_gettable(state, index);
	if (lua_isnil(state, -1)) {
		char tempStr[1024];
		sprintf(tempStr,"Function '%.*s' not found!\n", len - pos, buffer);
		ldb_write(get_luastate(), tempStr);
	}
	else if (lua_iscfunction(state, -1)) {
		char tempStr[1024];
		sprintf(tempStr,"Cannot break on C Function '%.*s'.\n", len - pos, buffer);
		ldb_write(get_luastate(), tempStr);
	}
	else if (lua_isfunction(state, -1)) {
		char tmp[512];
		int bk;
		strcpy(tmp, buffer);

		if (p == &(input->buffer[1][0])) {
			bk = add_func_break_point(ldb, tmp, "golbal");
		}
		else {
			bk = add_func_break_point(ldb, tmp, "local");
		}
		if (bk >= 0) {
			char tempStr[1024];
			sprintf(tempStr,"breakpoint %d is set at '%s'\n", bk, input->buffer[1]);
			ldb_write(get_luastate(), tempStr);

			enable_func_hook(state, ldb, 1);
		}
	}
	else {
		char tempStr[1024];
		sprintf(tempStr,"'%.*s' is not a function!\n", len - pos, buffer);
		ldb_write(get_luastate(), tempStr);
	}

	lua_settop(state, top);
}

static int search_local_var(lua_State *state, lua_Debug *ar, const char* var) {
	int         i;
	const char *name;

	for (i = 1; (name = lua_getlocal(state, ar, i)) != NULL; i++) {
		if (strcmp(var, name) == 0) {
			return i;
		}
		// not match, pop out the var's value
		lua_pop(state, 1);
	}
	return 0;
}

static int search_global_var(lua_State *state, lua_Debug *ar, const char* var) {
	lua_getglobal(state, var);

	if (lua_type(state, -1) == LUA_TNIL) {
		lua_pop(state, 1);
		return 0;
	}

	return 1;
}

static int list_handler(lua_State *state, ldb_t *ldb,lua_Debug *ar, input_t *input) {
	ldb_file_t *file;
	int         i, j;

	/* ignore `@` char */
	file = ldb_file_load(ldb, ar->source + 1);
	if (file == NULL) {
		return 0;
	}

	i = ar->currentline - 5;
	if (i < 0) {
		i = 0;
	}
	for (; i < ar->currentline; ++i) {
		char tempstr[1024];
		sprintf(tempstr,"%s:%d\t%s", file->name, i + 1, file->lines[i]);
		ldb_write(get_luastate(), tempstr);
	}
	for (i = ar->currentline, j = 0; j < 6 && i < file->line; ++j, ++i) {
		char tempstr[1024];
		sprintf(tempstr,"%s:%d\t%s", file->name, i + 1, file->lines[i]);
		ldb_write(get_luastate(), tempstr);
	}
	return 0;
}

static void
print_current_line(ldb_t *ldb, lua_Debug *ar) {
	ldb_file_t *file;
	int i;

	/* ignore `@` char */
	file = ldb_file_load(ldb, ar->source + 1);
	if (file == NULL) {
		return;
	}
	if (ar->currentline >= file->line) {
		return;
	}
	i = ar->currentline;

	char tempstr[1024];
	sprintf(tempstr,"%d\t%s", i + 1, file->lines[i]);
	ldb_write(get_luastate(), tempstr);
}

static void
step_in(lua_State *state, ldb_t *ldb,
	int depth, lua_Debug *ar) {
	if (ldb->step != 1) {
		single_step(ldb, 1);
	}
	if (depth == -1) {
		ldb->call_depth = -1;
	}
	print_current_line(ldb, ar);
}


static int
next_handler(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	step_in(state, ldb, 0, ar);

	return -1;
}

static void print_string_var(lua_State *state, int si, int depth) {
	ldb_write(get_luastate(), "\"");

	const char* val = lua_tostring(state, si);
	int vallen = strlen(val);
	int i;
	const char spchar[] = "\"\t\n\r";
	for (i = 0; i < vallen; ) {
		if (val[i] == 0) {
			ldb_write(get_luastate(), "\\000");
			++i;
		}
		else if (val[i] == '"') {
			ldb_write(get_luastate(), "\\\"");
			++i;
		}
		else if (val[i] == '\\') {
			ldb_write(get_luastate(), "\\\\");
			++i;
		}
		else if (val[i] == '\t') {
			ldb_write(get_luastate(), "\\t");
			++i;
		}
		else if (val[i] == '\n') {
			ldb_write(get_luastate(), "\\n");
			++i;
		}
		else if (val[i] == '\r') {
			ldb_write(get_luastate(), "\\r");
			++i;
		}
		else {
			int splen = strcspn(val + i, spchar);

			char tempStr[1024];
			sprintf(tempStr,"%.*s", splen, val + i);
			ldb_write(get_luastate(), tempStr);
			i += splen;
		}
	}

	ldb_write(get_luastate(), "\"");
}

static int break_handler(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	char *dv;

	if (input->num == 1) {
		ldb_write(get_luastate(), "usage:\n b(break) function\nb(break) filename:line\n");
		return 0;
	}

	dv = strchr(input->buffer[1], ':');
	if (dv) {
		handle_line_break(state, ldb, ar, input, dv);
	}
	else {
		handle_func_break(state, ldb, ar, input);
	}
	return 0;
}

static void print_var(lua_State *state, int si, int depth) {
	int type;

	char tmpStr[1024];
	type = lua_type(state, si);
	switch (type) {
	case LUA_TNIL:
		ldb_write(get_luastate(), "(nil)");
		break;

	case LUA_TNUMBER:		
		sprintf(tmpStr,"%f", lua_tonumber(state, si));
		ldb_write(get_luastate(), tmpStr);
		break;

	case LUA_TBOOLEAN:
		sprintf(tmpStr,"%s", lua_toboolean(state, si) ? "true" : "false");
		ldb_write(get_luastate(), tmpStr);
		break;

	case LUA_TFUNCTION:
	{
		lua_CFunction func = lua_tocfunction(state, si);
		if (func != NULL) {
			char tmpStr[1024];
			sprintf(tmpStr,"(C function)%p", func);
			ldb_write(get_luastate(), tmpStr);
		}
		else {
			ldb_write(get_luastate(), "(function)");
		}
	}
	break;

	case LUA_TUSERDATA:
		sprintf(tmpStr,"(user data)%p", lua_touserdata(state, si));
		ldb_write(get_luastate(), tmpStr);
		break;

	case LUA_TLIGHTUSERDATA:
		sprintf(tmpStr,"(light user data)%p", lua_touserdata(state, si));
		ldb_write(get_luastate(), tmpStr);
		break;

	case LUA_TSTRING:
		print_string_var(state, si, depth);
		break;

	case LUA_TTABLE:
		print_table_var(state, si, depth);
		break;

	default:
		break;
	}
}

static void print_table_var(lua_State *state, int si, int depth) {
	int pos_si = si > 0 ? si : (si - 1);
	ldb_write(get_luastate(), "{");
	int top = lua_gettop(state);
	lua_pushnil(state);
	int empty = 1;
	while (lua_next(state, pos_si) != 0) {
		if (empty) {
			ldb_write(get_luastate(), "\n");
			empty = 0;
		}

		int i;
		for (i = 0; i < depth; i++) {
			ldb_write(get_luastate(), "\t");
		}

		ldb_write(get_luastate(), "[");
		print_var(state, -2, -1);
		ldb_write(get_luastate(), "] = ");
		if (depth > 5) {
			ldb_write(get_luastate(), "{...}");
		}
		else {
			print_var(state, -1, depth + 1);
		}
		lua_pop(state, 1);
		ldb_write(get_luastate(), ",\n");
	}

	if (empty) {
		ldb_write(get_luastate(), " }");
	}
	else {
		int i;
		for (i = 0; i < depth - 1; i++) {
			ldb_write(get_luastate(), "\t");
		}

		ldb_write(get_luastate(), "}");
	}
	lua_settop(state, top);
}

static int print_handler(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	if (input->num < 2) {
		ldb_write(get_luastate(), "usage: p <varname>\n");
		return 0;
	}

	if (search_local_var(state, ar, input->buffer[1])) {
		char tmpStr[1024];
		sprintf(tmpStr,"local %s = ", input->buffer[1]);
		ldb_write(get_luastate(), tmpStr);
		print_var(state, -1, -1);
		lua_pop(state, 1);
		ldb_write(get_luastate(), "\n");
	}
	else if (search_global_var(state, ar, input->buffer[1])) {
		char tmpStr[1024];
		sprintf(tmpStr, "global %s = ", input->buffer[1]);
		ldb_write(get_luastate(), tmpStr);
		print_var(state, -1, -1);
		lua_pop(state, 1);
		ldb_write(get_luastate(), "\n");
	}
	else {
		char tempStr[1024];
		sprintf(tempStr,"not found var %s\n", input->buffer[1]);
		ldb_write(get_luastate(), tempStr);
	}

	return 0;
}

static void dump_stack(lua_State *state, int depth, int verbose) {
	lua_Debug ldb;
	int i;
	const char *name, *filename;

	for (i = depth; lua_getstack(state, i, &ldb) == 1; i++) {
		lua_getinfo(state, "Slnu", &ldb);
		name = ldb.name;
		if (!name) {
			name = "";
		}
		filename = ldb.source;

		char tempStr[1024];
		sprintf(tempStr, "#%d: %s:'%s', '%s' line %d\n",
			i + 1 - depth, ldb.what, name,
			filename, ldb.currentline);
		ldb_write(get_luastate(), tempStr);
	}
}

static int split_input(const char *buff, input_t *input) {
	const char *p, *save;
	int i;

	save = p = buff;
	save = NULL;
	i = 0;
	while (p && *p) {
		if (*p == '\n') {
			break;
		}

		if (isspace(*p)) {
			if (save) {
				if (i > LDB_MAX_PARAM) {
					char str[1024];
					sprintf(str, "param %s more than %d", buff, LDB_MAX_PARAM);
					ldb_output(__FILE__, __LINE__, str);
					return -1;
				}
				strncpy(input->buffer[i], save, p - save);
				input->buffer[i++][p - save] = '\0';
				save = NULL;
			}
		}
		else {
			if (save == NULL) {
				save = p;
			}
		}

		++p;
	}
	if (i > LDB_MAX_PARAM) {
		char str[1024];
		sprintf(str, "param %s more than %d", buff, LDB_MAX_PARAM);
		ldb_output(__FILE__, __LINE__, str);
		return -1;
	}
	if (save) {
		strcpy(input->buffer[i++], save);
	}
	input->num = i;

	/*
	ldb_output("input: ");
	int j = 0;
	for (j = 0; j < i; ++j) {
	  ldb_output("%s +", input[j]);
	}
	ldb_output("\n");
	*/

	return 0;
}

ldb_t* ldb_new(lua_State *state, ldb_reload_pt reload) {
	int    i;
	ldb_t *ldb;

	lua_gettable(state, LUA_REGISTRYINDEX);
	ldb = (ldb_t*)lua_touserdata(state, -1);
	/* already exist? */
	if (ldb) {
		return ldb;
	}

	ldb = (ldb_t*)allocBytes(sizeof(ldb_t));
	if (ldb == NULL) {
		return NULL;
	}
	ldb->step = 0;
	ldb->first = 1;
	ldb->call_depth = 0;
	ldb->state = state;
	ldb->reload = reload;

	lua_pushstring(state, lua_tag);
	lua_pushlightuserdata(state, ldb);
	lua_settable(state, LUA_REGISTRYINDEX);

	for (i = 0; i < MAX_FILE_BUCKET; ++i) {
		ldb->files[i] = NULL;
	}
	for (i = 0; i < MAX_BREAKPOINT; ++i) {
		ldb->bkpoints[i].available = 1;
		ldb->bkpoints[i].file = NULL;
		ldb->bkpoints[i].func = NULL;
	}
	ldb->bknum = 0;

	return ldb;
}

void ldb_free(ldb_t *ldb) {
	int         i;
	ldb_file_t *file, *next;
	lua_State  *state;
	ldb_breakpoint_t *bkpoint;

	state = ldb->state;
	for (i = 0; i < MAX_FILE_BUCKET; ++i) {
		file = ldb->files[i];
		while (file) {
			next = file->next;
			ldb_file_free(file);
			file = next;
		}
	}
	for (i = 0; i < MAX_BREAKPOINT; ++i) {
		bkpoint = &(ldb->bkpoints[i]);
		if (bkpoint->file) {
			deallocBytes(bkpoint->file);
		}
		if (bkpoint->func) {
			deallocBytes(bkpoint->file);
		}
	}
	deallocBytes(ldb);

	lua_pushstring(state, lua_tag);
	lua_pushlightuserdata(state, NULL);
	lua_settable(state, LUA_REGISTRYINDEX);
}

static int exit_debugger(lua_State *state, ldb_t *ldb, lua_Debug *ar, input_t *input)
{
	ldb_write(get_luastate(), "exit debugger sucessed!");

	return -2;
}

static int help_handler(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	int i;

	ldb_write(get_luastate(), "\n");
	for (i = 0; commands[i].name != NULL; ++i) {
		if (commands[i].help) 
		{
			char tmpStr[1024];
			sprintf(tmpStr, "%s(%s) %s \n", commands[i].name, commands[i].short_name, commands[i].help);
			//ldb_output(__FILE__, __LINE__, tmpStr);
			ldb_write(get_luastate(), tmpStr);
		}
	}
	return 0;
}

static int backtrace_handler(lua_State *state, ldb_t *ldb,
	lua_Debug *ar, input_t *input) {
	dump_stack(state, 0, 0);

	return 0;
}


static int get_calldepth(lua_State *state) {
	int i;
	lua_Debug ar;

	for (i = 0; lua_getstack(state, i + 1, &ar) != 0; i++)
		;
	return i;
}

static int search_break_point(ldb_t *ldb, lua_Debug *ar) {
	int i;
	ldb_breakpoint_t *bkpoint;

	/* search break point by file:line */
	for (i = 0; i < ldb->bknum; ++i) {
		bkpoint = &(ldb->bkpoints[i]);
		if (bkpoint->available) {
			continue;
		}
		if (bkpoint->active == 0) {
			continue;
		}
		if (bkpoint->file == NULL) {
			continue;
		}
		if (ar->currentline != bkpoint->line + 1) {
			continue;
		}
		if (strcmp(ar->source + 1, bkpoint->file)) {
			continue;
		}

		bkpoint->hit += 1;
		return i;
	}

	/* if not hook by func call, return */
	if (ar->event != LUA_HOOKCALL) {
		return -1;
	}
	if (ar->name == NULL) {
		return -1;
	}
	/* search break point by func */
	for (i = 0; i < ldb->bknum; ++i) {
		bkpoint = &(ldb->bkpoints[i]);
		if (bkpoint->available) {
			continue;
		}
		if (bkpoint->active == 0) {
			continue;
		}
		if (strcmp(ar->name, bkpoint->func)) {
			continue;
		}

		bkpoint->hit += 1;
		return i;
	}
	return -1;
}

/*static int get_input(char **buff) {
	*buff = readline(prompt);
	int len = strlen(*buff);
	if (len > 0) {
		add_history(*buff);
		return len;
	}
	return len;
}*/

static void set_prompt() {
	//ldb_output(prompt);
}

static void on_event(int bp, ldb_t *ldb, lua_State *state, lua_Debug *ar) {
	int depth;

	depth = get_calldepth(state);
	if (bp < 0 && ldb->call_depth != -1 && depth > ldb->call_depth) {
		/* next command, just return */
		return;
	}
	ldb->call_depth = depth;

	if (bp >= 0) {
		char tmpStr[1024];
		sprintf(tmpStr, "Breakpoint %d hit!", bp);

		ldb_output(__FILE__, __LINE__, tmpStr);
	}

	set_prompt();
	input_t input;
	char buff[1024];
	int ret, i, len;

	ret = 0;

	while ((len = ldb_read(get_luastate(),buff)) >= 0) {
		if (len == 0) {
			continue;
		}
		if (split_input(buff, &input) < 0) {
			set_prompt();
			continue;
		}

		ret = 0;
		for (i = 0; commands[i].handler != NULL; ++i) {
			if (!strcmp(input.buffer[0], commands[i].short_name) ||
				!strcmp(input.buffer[0], commands[i].name)) {
				ldb->call_depth = get_calldepth(state);
				ret = (*commands[i].handler)(state, ldb, ar, &input);
				break;
			}
		}
		if (commands[i].name == NULL) {
			char str[1024];
			sprintf(str, "bad command: %s, ldb_output h for help\n", buff);
			ldb_write(get_luastate(), str);
		}

		if (ret < 0) {
			break;
		}

		set_prompt();
	}

	if (ret == -2)
	{
		lua_sethook(get_luastate(), NULL, 0, 0);  /* reset hook */
		ldb_reset(get_luastate());
	}
}

static void all_hook(lua_State *state, lua_Debug *ar) {
	int    index;
	ldb_t  *ldb;

	if (!lua_getstack(state, 0, ar)) {
		ldb_output(__FILE__, __LINE__, "[LUA_DEBUG]lua_getstack fail");
		return;
	}

	lua_pushstring(state, lua_tag);
	lua_gettable(state, LUA_REGISTRYINDEX);
	ldb = (ldb_t*)lua_touserdata(state, -1);
	if (ldb == NULL) {
		return;
	}

	//if(lua_getinfo(state, "lnS", ar)) {
	if (lua_getinfo(state, "lnSu", ar)) {
		if (ldb->step) {
			if (ldb->first) {
				ldb->first = 0;

				char tmpStr[1024];
				sprintf(tmpStr, "Break at %s:%d", ar->source+1, ar->currentline);

				ldb_output(__FILE__, __LINE__,tmpStr);
			}
			on_event(-1, ldb, state, ar);
			return;
		}
		else {
			index = search_break_point(ldb, ar);
			if (index < 0) {
				return;
			}
			on_event(index, ldb, state, ar);
			return;
		}
	}
	else {
		ldb_output(__FILE__, __LINE__, "[LUA_DEBUG]lua_getinfo fail");
		return;
	}
}

static void enable_line_hook(lua_State *state, int enable) {
	int mask;

	mask = lua_gethookmask(state);
	if (enable) {
		lua_sethook(state, all_hook, mask | LUA_MASKLINE, 0);
	}
	else {
		lua_sethook(state, all_hook, mask & ~LUA_MASKLINE, 0);
	}
}

static void single_step(ldb_t *ldb, int step) {
	if (step) {
		enable_line_hook(ldb->state, 1);
	}

	ldb->step = step;
}

void    ldb_reset(lua_State *state)
{
	ldb_t *ldb;

	lua_pushstring(state, lua_tag);
	lua_gettable(state, LUA_REGISTRYINDEX);
	ldb = (ldb_t*)lua_touserdata(state, -1);
	if (ldb == NULL) {
		return;
	}

	ldb->step = 0;
	ldb->first = 1;
	ldb->call_depth = 0;
}

void ldb_break(lua_State *state) {
	ldb_t *ldb;

	lua_pushstring(state, lua_tag);
	lua_gettable(state, LUA_REGISTRYINDEX);
	ldb = (ldb_t*)lua_touserdata(state, -1);
	if (ldb == NULL) {
		return;
	}

	ldb->state = state;
	if (ldb->step == 0) {
		single_step(ldb, 1);
	}

	ldb->step = 1;
	ldb->first = 1;
	ldb->call_depth = -1;
}

void    ldb_startsocket(lua_State *state, const char *ipaddr, int port)
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		printf("WSAStartup failed with error: %d\n", err);
		return;
	}

	debugsocket = (struct socket*)allocBytes(sizeof(struct socket));
	debugsocket->listen_fd = -1;
	debugsocket->fd = -1;
	debugsocket->closed = 0;

	int lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int reuse = 1;
	setsockopt(debugsocket->listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(int));

	sockaddr_in service;

	service.sin_family = AF_INET;
	service.sin_addr.s_addr = inet_addr(ipaddr);
	service.sin_port = htons(port);

	if (bind(lfd, (const struct sockaddr *)&service, sizeof(service)) < 0) {
		closesocket(lfd);
		printf("bind() failed");
		exit(1);
	}
	if (listen(lfd, 1) < 0) {
		printf("listen(): Error");
		exit(1);
	}

	debugsocket->listen_fd = lfd;
}

static int fdcanread(int fd) {
	fd_set rfds;
	struct timeval tv = { 0,0 };

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	return select(fd + 1, &rfds, NULL, NULL, &tv) == 1;
}

static int test(struct socket *s, const char * welcome, size_t sz) {
	if (s->closed) {
		closesocket(s->fd);
		s->fd = -1;
		s->closed = 0;
	}
	if (s->fd < 0) {
		if (fdcanread(s->listen_fd)) {
			s->fd = accept(s->listen_fd, NULL, NULL);
			if (s->fd < 0) {
				return -1;
			}
			send(s->fd, welcome, sz, 0);
		}
	}
	if (fdcanread(s->fd)) {
		return s->fd;
	}

	return -1;
}

void    ldb_closesocket(void)
{
	if (debugsocket == NULL)
		return;

	if (debugsocket->closed) {
		closesocket(debugsocket->fd);
		debugsocket->fd = -1;
		debugsocket->closed = 0;
	}

	deallocBytes(debugsocket);
	debugsocket = NULL;
}

int ldb_read(lua_State *L, char *buf)
{
	if (debugsocket == NULL || debugsocket->listen_fd < 0) {
		return luaL_error(L, "start socket first");
	}

	memset(buf, 0, 1024);

	size_t sz = 0;
	const char * welcome = "lua debugger v1.0";
	int fd = test(debugsocket, welcome,strlen(welcome));
	if (fd >= 0) {
		//char buffer[BUFFER_SIZE];
		int rd = recv(fd, buf, BUFFER_SIZE, 0);
		if (rd <= 0) {
			debugsocket->closed = 1;
			//lua_pushboolean(L, 0);
			return 1;
		}
		//lua_pushlstring(L, buf, rd);
		return rd;
	}

	return 0;
}

int ldb_write(lua_State *L, const char *message)
{
	if (debugsocket == NULL || debugsocket->listen_fd < 0 || debugsocket->fd < 0 || message == NULL) {
		return luaL_error(L, "start socket first");
	}

	int p = 0;
	for (;;) {
		int wt = send(debugsocket->fd, message + p, strlen(message) - p, 0);
		if (wt < 0) {
			switch (errno) {
			case EWOULDBLOCK:
			case EINTR:
				continue;
			default:
				closesocket(debugsocket->fd);
				debugsocket->fd = -1;
				return 1;
			}
		}
		if (wt == strlen(message) - p)
			break;
		p += wt;
	}

	if (debugsocket->closed) {
		closesocket(debugsocket->fd);
		debugsocket->fd = -1;
		debugsocket->closed = 0;
	}

	return 0;
}
